/*
 * Copyright (C) 2011, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include "VCMixer.h"
#include "VCMixerFunc.h"

VCMixerNode::VCMixerNode(VCIController & controller, VCMixer & mixer)
: VCIMod(controller)
, m_stream_out(false)
, m_timer_in(0)
, m_channel(0)
, m_mixer(mixer)
{
}

VCMixerNode::~VCMixerNode()
{
	if (m_stream_out)
	{
		if (m_reciver)
		{
			m_reciver->Push(VC_STREAM_END, NULL, 0, VC_PCM_1_32000_S16);
		}
	}
}

VC_RESULT VCMixerNode::Push(VC_CMD cmd, void * data_buff, size_t data_size, VC_DATAFORMAT data_format)
{
	VC_RESULT result = VC_FALSE;
	switch(cmd)
	{
	case VC_STREAM_BEGIN:
		if (data_format == VC_PCM_1_32000_S16)
		{
			VCAutoMutex lock(m_buff_mutex);
			m_timer_in = VCTimeMS();
			if (data_buff && data_size)
			{
				m_buff.Push(data_buff, data_size);
			}
			result = VC_OK;
		}
		break;
	case VC_STREAM:
		if (data_format == VC_PCM_1_32000_S16)
		{
			VCAutoMutex lock(m_buff_mutex);
			if (data_buff && data_size)
			{
				m_buff.Push(data_buff, data_size);
			}
			if (m_timer_in)
			{
				m_timer_in = VCTimeMS();
			} else
			{
				if (m_buff.Size() >= VCMIXERCHANEL_SIZE)
				{
					m_timer_in = VCTimeMS();
				}
			}
			result = VC_OK;
		}
		break;
	case VC_STREAM_END:
		if (data_format == VC_PCM_1_32000_S16)
		{
			VCAutoMutex lock(m_buff_mutex);
			m_timer_in = 0;
			if (data_buff && data_size)
			{
				m_buff.Push(data_buff, data_size);
			}
			result = VC_OK;
		}
		break;
	case VC_SETCHANNEL:
		if (data_buff && (data_size == sizeof(char)) && (data_format == VC_CMD_DF))
		{
			char channel = *((char *)data_buff);
			m_mixer.SetChannelNode(this, channel);
		}
	default:
		if (m_reciver)
		{
			result = m_reciver->Push(cmd, data_buff, data_size, data_format);
		}
		break;
	}
	return result;
}

VCMixerChannel::VCMixerChannel(VCMixer & mixer, char channel)
: VCThread(NULL)
, m_needexit(false)
, m_mixer(mixer)
, m_channel(channel)
{
}

VCMixerChannel::~VCMixerChannel()
{
	m_needexit = true;
	Wait();
	{
		VCAutoMutex lock(m_nodes_mutex);
		std::list<VCMixerNode *>::iterator it = m_nodes.begin();
		while (it != m_nodes.end())
		{
			VCMixerNode * node = *it;
			if (node != NULL)
			{
				delete node;
			}
			it++;
		}
		m_nodes.clear();
	}
}

void VCMixerChannel::RememberNode(VCMixerNode * node)
{
	if (node)
	{
		VCAutoMutex lock(m_nodes_mutex);
		bool find = false;
		std::list<VCMixerNode *>::iterator it = m_nodes.begin();
		while (it != m_nodes.end())
		{
			if (*it == node)
			{
				find = true;
				break;
			}
			it++;
		}
		if (!find)
		{
			TRACE("mixer channel[%hhi] remember node[0x%x]", m_channel, node)
			m_nodes.push_back(node);
			node->m_channel = m_channel;
		}
	}
}

bool VCMixerChannel::ForgetNode(VCMixerNode * node)
{
	bool find = false;
	if (node)
	{
		VCAutoMutex lock(m_nodes_mutex);
		std::list<VCMixerNode *>::iterator it = m_nodes.begin();
		while (it != m_nodes.end())
		{
			if (*it == node)
			{
				TRACE("mixer channel[%hhi] forget node[0x%x]", m_channel, node)
				find = true;
				m_nodes.erase(it);
				node->m_channel = -1;
				if (node->m_stream_out)
				{
					node->m_stream_out = false;
					if (node->m_reciver)
					{
						node->m_reciver->Push(VC_STREAM_END, NULL, 0, VC_PCM_1_32000_S16);
					}
				}
				break;
			}
			it++;
		}
	}
	return find;
}

VCMixerNode * VCMixerChannel::NewNode(VCIController & controller)
{
	VCMixerNode * result = new VCMixerNode(controller, m_mixer);
	if (result)
	{
		TRACE("mixer channel[%hhi] new node[0x%x]", m_channel, result)
		result->m_channel = m_channel;
		VCAutoMutex lock(m_nodes_mutex);
		m_nodes.push_back(result);
	}
	return result;
}

void VCMixerChannel::DeleteNode(VCMixerNode * node)
{
	if (node)
	{
		VCAutoMutex lock(m_nodes_mutex);
		std::list<VCMixerNode *>::iterator it = m_nodes.begin();
		while (it != m_nodes.end())
		{
			VCMixerNode * mnode = *it;
			if ((mnode != NULL) && (mnode == node))
			{
				TRACE("mixer channel[%hhi] delete node[0x%x]", m_channel, node)
				m_nodes.erase(it);
				delete node;
				break;
			}
			it++;
		}
	}
}

int VCMixerChannel::Execute(void * data)
{
	unsigned long time_cur;
	size_t mix_size = VCMIXERCHANEL_SIZE * sizeof(short);
	size_t mix_count = VCMIXERCHANEL_SIZE;
	bool mix;
	size_t mix_nodes;
	bool stream_off = true;
	short buff_out[VCMIXERCHANEL_SIZE];
	while (!m_needexit)
	{
		VCAutoMutex lock(m_nodes_mutex);
		time_cur = VCTimeMS();
		mix = true;
		mix_nodes = 0;
		std::list<VCMixerNode *>::iterator it = m_nodes.begin();
		while (it != m_nodes.end())
		{
			VCMixerNode * node = *it;
			if (node)
			{
				VCAutoMutex(node->m_buff_mutex);
				if (node->m_timer_in)
				{
					if (node->m_timer_in + VCMIXERCHANEL_TIME < time_cur)
					{
						node->m_timer_in = 0;
					} else
					if (node->m_buff.Size() < mix_size)
					{
						mix = false;
					} else
					{
						mix_nodes++;
						stream_off = false;
					}
				}
			}
			it++;
		}
		// mix sound
		if (mix)
		{
			mix = false;
			memset(buff_out, 0, sizeof(buff_out));
			it = m_nodes.begin();
			while (it != m_nodes.end())
			{
				VCMixerNode * node = *it;
				if (node)
				{
					VCAutoMutex(node->m_buff_mutex);
					if (node->m_timer_in)
					{
						mix = true;
						DEF_MIXFUNC(buff_out, (short *)node->m_buff.GetDataPtr(), mix_count, mix_nodes);
						node->m_buff.PopSize(mix_size);
					}
				}
				it++;
			}
		}
		// send sound
		if (mix || stream_off)
		{
			it = m_nodes.begin();
			while (it != m_nodes.end())
			{
				VCMixerNode * node = *it;
				if (node)
				{
					if (stream_off)
					{
						if (node->m_stream_out)
						{
							node->m_stream_out = false;
							if (node->m_reciver)
							{
								node->m_reciver->Push(VC_STREAM_END, NULL, 0, VC_PCM_1_32000_S16);
							}
						}
					} else
					if (mix)
					{
						if (!node->m_stream_out)
						{
							node->m_stream_out = true;
							if (node->m_reciver)
							{
								node->m_reciver->Push(VC_STREAM_BEGIN, NULL, 0, VC_PCM_1_32000_S16);
							}
						}
						if (node->m_reciver)
						{
							node->m_reciver->Push(VC_STREAM, buff_out, mix_size, VC_PCM_1_32000_S16);
						}
					}
				}
				it++;
			}
		}
		VCSleep(1);
	}
	return 0;
}

void VCMixerChannel::OnExit(int exitcode)
{
}

VCMixer::VCMixer()
{
}

VCMixer::~VCMixer()
{
	VCAutoMutex lock(m_channels_mutex);
	std::map<char, VCMixerChannel *>::iterator it = m_channels.begin();
	while (it != m_channels.end())
	{
		VCMixerChannel * channel = it->second;
		if (channel != NULL)
		{
			delete channel;
		}
		it++;
	}
	m_channels.clear();
}

VCMixerNode * VCMixer::NewNode(VCIController & controller)
{
	VCAutoMutex lock(m_channels_mutex);
	VCMixerNode * result = NULL;
	VCMixerChannel * channel = GetChannel(0);
	if (channel)
	{
		result = channel->NewNode(controller);
	}
	return result;
}

void VCMixer::DeleteNode(VCMixerNode * node)
{
	if (node)
	{
		VCAutoMutex lock(m_channels_mutex);
		std::map<char, VCMixerChannel *>::iterator it = m_channels.begin();
		while (it != m_channels.end())
		{
			VCMixerChannel * channel = it->second;
			channel->DeleteNode(node);
			it++;
		}
	}
}

void VCMixer::SetChannelNode(VCMixerNode * node, char to_channel)
{
	if (node)
	{
		VCAutoMutex lock(m_channels_mutex);
		std::map<char, VCMixerChannel *>::iterator it = m_channels.begin();
		while (it != m_channels.end())
		{
			VCMixerChannel * channel = it->second;
			channel->ForgetNode(node);
			it++;
		}
		VCMixerChannel * channel = GetChannel(to_channel);
		if (channel)
		{
			channel->RememberNode(node);
		}
	}
}

VCMixerChannel * VCMixer::GetChannel(char channel)
{
	if (channel < 0)
	{
		channel = 0;
	}
	TRACE("mixer get channel[%hhi]", channel)
	VCMixerChannel * result = NULL;
	if (m_channels.count(channel) > 0)
	{
		result = m_channels[channel];
	} else
	{
		result = new VCMixerChannel(*this, channel);
		if (result)
		{
			m_channels[channel] = result;
		}
	}
	return result;
}

